package jackson.util;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;

import java.time.ZoneId;
import java.util.Locale;
import java.util.TimeZone;

import static constant.DateTimeConstant.DATE_TIME_FORMAT;
import static jackson.JacksonAutoConfiguration.javaTimeModule;

public abstract class JSONUtil {
    private static final ObjectMapper OBJECT_MAPPER;

    static {
        // JsonMapper JSON_MAPPER = JsonMapper.builder()
        //         .defaultLocale(Locale.CHINA)
        //         .defaultTimeZone(TimeZone.getTimeZone(ZoneId.systemDefault()))
        //         .defaultDateFormat(new SimpleDateFormat(DATE_TIME_FORMAT))
        //         .addModules(new SimpleModule().addSerializer(Long.class, ToStringSerializer.instance), javaTimeModule())
        //         .enable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING)
        //         .enable(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE)
        //         .configure(JsonReadFeature.ALLOW_SINGLE_QUOTES, true)
        //         .build();

        Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
        builder.locale(Locale.CHINA);
        // builder.timeZone(TimeZone.getTimeZone(ASIA_SHANGHAI));
        builder.timeZone(TimeZone.getTimeZone(ZoneId.systemDefault()));
        builder.simpleDateFormat(DATE_TIME_FORMAT);
        builder.serializerByType(Long.class, ToStringSerializer.instance);
        builder.modules(javaTimeModule());

        // 序列化枚举值为数据库存储值，https://mybatis-plus.com/guide/enum.html#jackson
        builder.featuresToEnable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING);
        // @JsonEnumDefaultValue 反序列化未知枚举值的默认值。用于字段上，要启用 READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE
        // https://github.com/FasterXML/jackson-databind/wiki/Deserialization-Features#value-conversions-coercion
        builder.featuresToEnable(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE);

        OBJECT_MAPPER = builder.createXmlMapper(false).build();
    }

    /**
     * 对象转 json
     */
    public static <T> String obj2String(T obj) {
        try {
            return obj instanceof String ? (String) obj : OBJECT_MAPPER.writeValueAsString(obj);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 对象转 json，带格式化
     */
    public static <T> String obj2StringPretty(T obj) {
        try {
            return obj instanceof String ? (String) obj : OBJECT_MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(obj);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * json 转对象
     */
    @SuppressWarnings("unchecked")
    public static <T> T string2Obj(String str, Class<T> clazz) {
        try {
            return String.class.equals(clazz) ? (T) str : OBJECT_MAPPER.readValue(str, clazz);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * json 转嵌套对象，如 Map<User, Category>、List<>、...
     */
    @SuppressWarnings("unchecked")
    public static <T> T string2Obj(String str, TypeReference<T> typeReference) {
        try {
            return (T) (String.class.equals(typeReference.getType()) ? str : OBJECT_MAPPER.readValue(str, typeReference));
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * json 转嵌套对象，如 Map<User, Category>、List<>、...
     */
    public static <T> T string2Obj(String str, Class<?> collectionClass, Class<?>... elementClasses) {
        JavaType javaType = OBJECT_MAPPER.getTypeFactory().constructParametricType(collectionClass, elementClasses);
        try {
            return OBJECT_MAPPER.readValue(str, javaType);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
}